// Protocol Buffers for GWT
// Copyright 2008 Google Inc.  All rights reserved.
// Copyright 2010 Thomas Broyer  All rights reserved.
// http://code.google.com/p/protobuf-gwt/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf;

import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.Message.Builder;

import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;

/**
 * @author t.broyer@ltgt.net Thomas Broyer
 * Based on the initial work of:
 * @author kenton@google.com Kenton Varda
 */
public final class Descriptors {
  private interface GenericDescriptor {
    String getName();
    String getFullName();
    FileDescriptor getFile();
    int getIndex();
  }

  public static abstract class FileDescriptor {
    
    private final String name;
    private final String packageName;
    private List<Descriptor> messageTypes;
    private List<EnumDescriptor> enumTypes;
    private List<ServiceDescriptor> services;
    private List<FileDescriptor> dependencies;
    
    protected FileDescriptor(String name, String packageName) {
      this.name = name;
      this.packageName = packageName;
    }
    
    public final String getName() {
      return name;
    }
    
    public final String getPackage() {
      return packageName;
    }
    
    public final List<Descriptor> getMessageTypes() {
      if (messageTypes == null) {
        messageTypes = createMessageTypes();
      }
      return messageTypes;
    }
    public final List<EnumDescriptor> getEnumTypes() {
      if (enumTypes == null) {
        enumTypes = createEnumTypes();
      }
      return enumTypes;
    }
    public final List<ServiceDescriptor> getServices() {
      if (services == null) {
        services = createServices();
      }
      return services;
    }
    public final List<FileDescriptor> getDependencies() {
      if (dependencies == null) {
        dependencies = Collections.unmodifiableList(Arrays.asList(createDependencies()));
      }
      return dependencies;
    }

    protected abstract List<Descriptor> createMessageTypes();
    protected abstract List<EnumDescriptor> createEnumTypes();
    protected abstract List<ServiceDescriptor> createServices();
    protected abstract FileDescriptor[] createDependencies();
    
    public Descriptor findMessageTypeByName(String name) {
      for (Descriptor descriptor : getMessageTypes()) {
        if (name.equals(descriptor.getName())) {
          return descriptor;
        }
      }
      return null;
    }
    
    public EnumDescriptor findEnumTypeByName(String name) {
      for (EnumDescriptor enumDescriptor : getEnumTypes()) {
        if (name.equals(enumDescriptor.getName())) {
          return enumDescriptor;
        }
      }
      return null;
    }

    public ServiceDescriptor findServiceByName(String name) {
      for (ServiceDescriptor service : getServices()) {
        if (name.equals(service.getName())) {
          return service;
        }
      }
      return null;
    }
  }
  
  public static abstract class DescriptorList<T extends GenericDescriptor> extends AbstractList<T> implements RandomAccess {

    private final FileDescriptor file;
    private final int size;
    
    protected DescriptorList(FileDescriptor file, int size) {
      this.file = file;
      this.size = size;
    }
    
    @Override
    public int indexOf(Object o) {
      if (o instanceof GenericDescriptor) {
        GenericDescriptor descriptor = (GenericDescriptor) o;
        if (descriptor.getFile() == file) {
          return descriptor.getIndex();
        }
      }
      return -1;
    }
    
    @Override
    public T get(int index) {
      if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException();
      }
      return getDescriptor(index);
    }

    @Override
    public final int size() { return size; }
    
    protected abstract T getDescriptor(int index);
  }
  
  public static abstract class Descriptor implements GenericDescriptor {
    private String fullName;
    private FieldDescriptor[] fields;
    private Descriptor[] nestedTypes;
    private EnumDescriptor[] enumTypes;

    protected Descriptor[] ensureNestedTypes() {
      if (nestedTypes == null) {
        nestedTypes = createNestedTypes();
      }
      return nestedTypes;
    }

    protected abstract Descriptor[] createNestedTypes();

    public Descriptor findNestedTypeByName(final String name) {
      for (Descriptor nestedType : ensureNestedTypes()) {
        if (name.equals(nestedType.getName())) {
          return nestedType;
        }
      }
      return null;
    }

    protected EnumDescriptor[] ensureEnumTypes() {
      if (enumTypes == null) {
        enumTypes = createEnumTypes();
      }
      return enumTypes;
    }

    protected abstract EnumDescriptor[] createEnumTypes();

    public EnumDescriptor findEnumTypeByName(final String name) {
      for (EnumDescriptor enumType : ensureEnumTypes()) {
        if (name.equals(enumType.getName())) {
          return enumType;
        }
      }
      return null;
    }

    protected FieldDescriptor[] ensureFields() {
      if (fields == null) {
        fields = createFields();
      }
      return fields;
    }

    protected abstract FieldDescriptor[] createFields();

    public FieldDescriptor findFieldByName(final String name) {
      for (FieldDescriptor field : ensureFields()) {
        if (name.equals(field.getName())) {
          return field;
        }
      }
      return null;
    }

    public FieldDescriptor findFieldByNumber(final int number) {
      for (FieldDescriptor field : ensureFields()) {
        if (number == field.getNumber()) {
          return field;
        }
      }
      return null;
    }
    
    // Override in top-level-message descriptor
    public FileDescriptor getFile() {
      return getContainingType().getFile();
    }

    public abstract int getIndex();

    public abstract String getName();

    // Override in top-level-message descriptor
    public String getFullName() {
      if (fullName == null) {
        fullName = getContainingType().getFullName() + "." + getName();
      }
      return fullName;
    }

    // Override in nested-message descriptor
    public Descriptor getContainingType() {
      return null;
    }

    public final List<FieldDescriptor> getFields() {
      return Collections.unmodifiableList(Arrays.asList(ensureFields()));
    }

    public final List<Descriptor> getNestedTypes() {
      return Collections.unmodifiableList(Arrays.asList(ensureNestedTypes()));
    }

    public final List<EnumDescriptor> getEnumTypes() {
      return Collections.unmodifiableList(Arrays.asList(ensureEnumTypes()));
    }
  }

  public static abstract class FieldDescriptor implements
      GenericDescriptor, Comparable<FieldDescriptor> {

    public final int compareTo(final FieldDescriptor other) {
      return getNumber() - other.getNumber();
    }

    private final Descriptor containingType;
    private final String name;
    private final int tag;
    private final int index;
    private String fullName;

    public FieldDescriptor(Descriptor containingType, String name, int tag,
        int index) {
      this.containingType = containingType;
      this.name = name;
      this.tag = tag;
      this.index = index;
    }

    public final Descriptor getContainingType() {
      return containingType;
    }

    public final FileDescriptor getFile() {
      return containingType.getFile();
    }
    
    public final int getIndex() {
      return index;
    }

    public final String getName() {
      return name;
    }

    public final int getNumber() {
      return tag;
    }

    public final String getFullName() {
      if (fullName == null) {
        fullName = containingType.getFullName() + "." + name;
      }
      return fullName;
    }

    public abstract boolean isRequired();

    public abstract boolean isOptional();

    public abstract boolean isRepeated();

    public boolean hasDefaultValue() {
      return false;
    }

    public abstract Object getDefaultValue();

    public Descriptor getMessageType() {
      throw new UnsupportedOperationException();
    }

    public EnumDescriptor getEnumType() {
      throw new UnsupportedOperationException();
    }

    public Builder newBuilderForField() {
      throw new UnsupportedOperationException();
    }

    public abstract Type getType();

    public JavaType getJavaType() {
      return getType().getJavaType();
    }
    
    public enum Type {
      DOUBLE {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.DOUBLE;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_DOUBLE;
        }
      },
      FLOAT {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.FLOAT;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_FLOAT;
        }
      },
      INT64 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.LONG;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_INT64;
        }
      },
      UINT64 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.LONG;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_UINT64;
        }
      },
      INT32 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.INT;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_INT32;
        }
      },
      FIXED64 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.LONG;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_FIXED64;
        }
      },
      FIXED32 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.INT;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_FIXED32;
        }
      },
      BOOL {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.BOOLEAN;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_BOOL;
        }
      },
      STRING {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.STRING;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_STRING;
        }
      },
      GROUP {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.MESSAGE;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_GROUP;
        }
      },
      MESSAGE {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.MESSAGE;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_MESSAGE;
        }
      },
      BYTES {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.BYTE_STRING;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_BYTES;
        }
      },
      UINT32 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.INT;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_UINT32;
        }
      },
      ENUM {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.ENUM;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_ENUM;
        }
      },
      SFIXED32 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.INT;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_SFIXED32;
        }
      },
      SFIXED64 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.LONG;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_SFIXED64;
        }
      },
      SINT32 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.INT;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_SINT32;
        }
      },
      SINT64 {
        @Override
        public Descriptors.FieldDescriptor.JavaType getJavaType() {
          return JavaType.LONG;
        }

        @Override
        public FieldDescriptorProto.Type toProto() {
          return FieldDescriptorProto.Type.TYPE_SINT64;
        }
      };

      public abstract JavaType getJavaType();

      public abstract FieldDescriptorProto.Type toProto();

      public static Type valueOf(final FieldDescriptorProto.Type type) {
        return values()[type.getNumber() - 1];
      }
    }

    public enum JavaType {
      INT {
        @Override
        public Object getDefaultValue() {
          return 0;
        }
      },
      LONG {
        @Override
        public Object getDefaultValue() {
          return 0L;
        }
      },
      FLOAT {
        @Override
        public Object getDefaultValue() {
          return 0F;
        }
      },
      DOUBLE {
        @Override
        public Object getDefaultValue() {
          return 0D;
        }
      },
      BOOLEAN {
        @Override
        public Object getDefaultValue() {
          return false;
        }
      },
      STRING {
        @Override
        public Object getDefaultValue() {
          return "";
        }
      },
      BYTE_STRING {
        @Override
        public Object getDefaultValue() {
          return ByteString.EMPTY;
        }
      },
      ENUM {
        @Override
        public Object getDefaultValue() {
          return null;
        }
      },
      MESSAGE {
        @Override
        public Object getDefaultValue() {
          return null;
        }
      };

      public abstract Object getDefaultValue();
    }
  }

  public static abstract class SingularFieldDescriptor extends FieldDescriptor {

    private final boolean required;

    public SingularFieldDescriptor(Descriptor containingType, String name,
        int tag, int index, boolean required) {
      super(containingType, name, tag, index);
      this.required = required;
    }

    @Override
    public final boolean isRequired() {
      return required;
    }

    @Override
    public final boolean isOptional() {
      return !required;
    }

    @Override
    public final boolean isRepeated() {
      return false;
    }
  }

  public static abstract class EnumFieldDescriptor extends
      SingularFieldDescriptor {

    public EnumFieldDescriptor(Descriptor containingType, String name, int tag,
        int index, boolean required) {
      super(containingType, name, tag, index, required);
    }

    @Override
    public final Type getType() {
      return Type.ENUM;
    }

    @Override
    public abstract EnumValueDescriptor getDefaultValue();
    
    @Override
    public abstract EnumDescriptor getEnumType();
  }

  public static abstract class MessageFieldDescriptor extends
      SingularFieldDescriptor {

    public MessageFieldDescriptor(Descriptor containingType, String name,
        int tag, int index, boolean required) {
      super(containingType, name, tag, index, required);
    }

    @Override
    public final Object getDefaultValue() {
      throw new UnsupportedOperationException();
    }

    @Override
    public abstract Descriptor getMessageType();
    
    @Override
    public abstract Message.Builder newBuilderForField();

    @Override
    public Type getType() {
      return Type.MESSAGE;
    }
  }

  public static class PrimitiveFieldDescriptor extends SingularFieldDescriptor {

    private final Type type;
    private final Object defaultValue;

    public PrimitiveFieldDescriptor(Descriptor containingType, String name,
        int tag, int index, boolean required, Type type, Object defaultValue) {
      super(containingType, name, tag, index, required);
      assert type != null && type != Type.MESSAGE && type != Type.ENUM;
      this.type = type;
      this.defaultValue = defaultValue;
    }

    @Override
    public final boolean hasDefaultValue() {
      return defaultValue != null;
    }

    @Override
    public Object getDefaultValue() {
      return defaultValue != null ? defaultValue
          : getJavaType().getDefaultValue();
    }

    @Override
    public final Type getType() {
      return type;
    }
  }

  public static abstract class RepeatedFieldDescriptor extends FieldDescriptor {

    public RepeatedFieldDescriptor(Descriptor containingType, String name,
        int tag, int index) {
      super(containingType, name, tag, index);
    }

    @Override
    public final Object getDefaultValue() {
      return Collections.emptyList();
    }

    @Override
    public final boolean isRequired() {
      return false;
    }

    @Override
    public final boolean isOptional() {
      return false;
    }

    @Override
    public final boolean isRepeated() {
      return true;
    }
  }

  public static final class RepeatedPrimitiveFieldDescriptor extends
      RepeatedFieldDescriptor {

    private final Type type;

    public RepeatedPrimitiveFieldDescriptor(Descriptor containingType,
        String name, int tag, int index, Type type) {
      super(containingType, name, tag, index);
      assert type != null && type != Type.MESSAGE && type != Type.ENUM;
      this.type = type;
    }

    @Override
    public final Type getType() {
      return type;
    }
  }

  public static abstract class RepeatedMessageFieldDescriptor extends
      RepeatedFieldDescriptor {

    public RepeatedMessageFieldDescriptor(Descriptor containingType,
        String name, int tag, int index) {
      super(containingType, name, tag, index);
    }

    @Override
    public Type getType() {
      return Type.MESSAGE;
    }

    @Override
    public abstract Descriptor getMessageType();
    
    @Override
    public abstract Builder newBuilderForField();
  }

  public static abstract class RepeatedEnumFieldDescriptor extends
      RepeatedFieldDescriptor {

    public RepeatedEnumFieldDescriptor(Descriptor containingType, String name,
        int tag, int index) {
      super(containingType, name, tag, index);
    }

    @Override
    public final Type getType() {
      return Type.ENUM;
    }

    @Override
    public abstract EnumDescriptor getEnumType();
  }

  public static abstract class ServiceDescriptor implements GenericDescriptor {
    private MethodDescriptor[] methods;

    protected MethodDescriptor[] ensureMethods() {
      if (methods == null) {
        methods = createMethods();
      }
      return methods;
    }

    protected abstract MethodDescriptor[] createMethods();

    public abstract FileDescriptor getFile();
    
    public abstract int getIndex();

    public abstract String getName();

    public abstract String getFullName();

    public final List<MethodDescriptor> getMethods() {
      return Collections.unmodifiableList(Arrays.asList(ensureMethods()));
    }

    public MethodDescriptor findMethodByName(final String name) {
      for (MethodDescriptor method : ensureMethods()) {
        if (method.getName().equals(name)) {
          return method;
        }
      }
      return null;
    }
  }

  public static abstract class MethodDescriptor implements GenericDescriptor {
    private final String name;
    private final int index;
    private String fullName;
    private final ServiceDescriptor service;

    public MethodDescriptor(String name, int index, ServiceDescriptor service) {
      this.name = name;
      this.index = index;
      this.service = service;
    }
    
    public final FileDescriptor getFile() {
      return service.getFile();
    }

    public final int getIndex() {
      return index;
    }

    public final String getName() {
      return name;
    }

    public final String getFullName() {
      if (fullName == null) {
        fullName = service.getFullName() + "." + name;
      }
      return fullName;
    }

    public final ServiceDescriptor getService() {
      return service;
    }

    public abstract Descriptor getInputType();

    public abstract Descriptor getOutputType();
  }

  public static abstract class EnumDescriptor implements
      GenericDescriptor, Internal.EnumLiteMap<EnumValueDescriptor> {
    private String fullName;
    private EnumValueDescriptor[] values;

    protected EnumValueDescriptor[] ensureValues() {
      if (values == null) {
        values = createValues();
      }
      return values;
    }

    protected abstract EnumValueDescriptor[] createValues();

    public EnumValueDescriptor findValueByName(String name) {
      for (EnumValueDescriptor value : ensureValues()) {
        if (name.equals(value.getName())) {
          return value;
        }
      }
      return null;
    }

    public EnumValueDescriptor findValueByNumber(int number) {
      for (EnumValueDescriptor value : ensureValues()) {
        if (number == value.getNumber()) {
          return value;
        }
      }
      return null;
    }

    // Override in nested-enum descriptor
    public Descriptor getContainingType() {
      return null;
    }

    // Override in top-level-enum descriptor
    public String getFullName() {
      if (fullName == null) {
        fullName = getContainingType().getFullName() + "." + getName();
      }
      return fullName;
    }

    // Override in top-level-enum descriptor
    public FileDescriptor getFile() {
      return getContainingType().getFile();
    }
    
    public abstract int getIndex();

    public abstract String getName();

    public final List<EnumValueDescriptor> getValues() {
      return Collections.unmodifiableList(Arrays.asList(ensureValues()));
    }
  }

  public static final class EnumValueDescriptor implements GenericDescriptor, Internal.EnumLite {
    private final EnumDescriptor type;
    private final int index;
    private final String name;
    private final int tag;
    private String fullName;

    public EnumValueDescriptor(EnumDescriptor type, int index, String name,
        int tag) {
      this.type = type;
      this.index = index;
      this.name = name;
      this.tag = tag;
    }

    public final FileDescriptor getFile() {
      return type.getFile();
    }
    
    public final String getFullName() {
      if (fullName == null) {
        fullName = type.getFullName() + "." + getName();
      }
      return fullName;
    }

    public final int getIndex() {
      return index;
    }

    public final String getName() {
      return name;
    }

    public final int getNumber() {
      return tag;
    }

    public final EnumDescriptor getType() {
      return type;
    }
  }
}
